#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
股票一板进二板量化筛选系统 - 优化版

核心优化目标：提高一板进二板的成功概率，降低回撤风险
"""

import akshare as ak
import tushare as ts
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import time
import json
import warnings
import logging
import matplotlib.pyplot as plt
import seaborn as sns
warnings.filterwarnings('ignore')

# 设置中文字体以支持中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False


class EnhancedStockSelector:
    """
    股票一进二板优化筛选器
    """
    
    def __init__(self, config_path="config.json"):
        """
        初始化筛选器
        
        Parameters:
        config_path (str): 配置文件路径
        """
        # 设置日志
        self.setup_logging()
        
        # 读取配置文件
        with open(config_path, 'r', encoding='utf-8') as f:
            self.config = json.load(f)
            
        # 增强参数
        self.enhanced_params = self.config.get("enhanced_params", {
            "score_threshold": 75,
            "volume_ratio_range": [1.5, 3.0],
            "profit_ratio_min": 60,
            "chip_concentration_max": 15,
            "market_sentiment_min": 50,
            "plate_strength_rank": 10
        })
            
        self.token = self.config.get("tushare_token", "")
        if self.token:
            ts.set_token(self.token)
            self.pro = ts.pro_api()
        else:
            self.pro = None
            
    def setup_logging(self):
        """
        设置日志配置
        """
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler('stock_selection.log', encoding='utf-8'),
                logging.StreamHandler()
            ]
        )
        self.logger = logging.getLogger(__name__)
            
    def get_latest_limit_up_stocks(self, date=None):
        """
        获取最新涨停板股票数据
        
        Parameters:
        date (str): 日期，格式为YYYYMMDD
        
        Returns:
        DataFrame: 涨停板股票数据
        """
        if date is None:
            date = datetime.now().strftime("%Y%m%d")
            
        try:
            # 获取涨停股池
            df = ak.stock_zt_pool_em(date=date)
            self.logger.info(f"获取到 {len(df)} 只涨停股票数据")
            return df
        except Exception as e:
            self.logger.error(f"获取涨停板数据失败: {e}")
            return pd.DataFrame()
    
    def get_stock_limit_history(self, stock_code, period_days=90):
        """
        获取股票历史涨停次数
        
        Parameters:
        stock_code (str): 股票代码
        period_days (int): 统计周期（天）
        
        Returns:
        int: 涨停次数
        """
        try:
            end_date = datetime.now().strftime("%Y%m%d")
            start_date = (datetime.now() - timedelta(days=period_days)).strftime("%Y%m%d")
            
            # 获取历史交易数据
            df = ak.stock_zt_pool_em(date=end_date)
            # 这里应该实现实际的历史涨停统计逻辑
            # 由于akshare接口限制，这里简化处理
            count = np.random.randint(0, 5)  # 模拟返回历史涨停次数
            self.logger.debug(f"股票 {stock_code} 近 {period_days} 天涨停次数: {count}")
            return count
        except Exception as e:
            self.logger.error(f"获取股票{stock_code}历史涨停数据失败: {e}")
            return 0
    
    def get_stock_chip_distribution(self, stock_code):
        """
        获取股票筹码分布数据
        
        Parameters:
        stock_code (str): 股票代码
        
        Returns:
        dict: 筹码分布信息
        """
        try:
            # 获取筹码分布数据（模拟实现）
            # 实际应使用akshare的相关接口
            chip_data = {
                "profit_ratio": np.random.uniform(40, 90),  # 获利比例
                "concentration_90": np.random.uniform(5, 25),  # 90%筹码集中度
                "recent_unblock_pressure": False  # 近期解禁压力
            }
            self.logger.debug(f"股票 {stock_code} 筹码分布数据: {chip_data}")
            return chip_data
        except Exception as e:
            self.logger.error(f"获取股票{stock_code}筹码分布数据失败: {e}")
            return {
                "profit_ratio": 0,
                "concentration_90": 100,
                "recent_unblock_pressure": True
            }
    
    def get_stock_technical_indicators(self, stock_code):
        """
        获取股票技术指标
        
        Parameters:
        stock_code (str): 股票代码
        
        Returns:
        dict: 技术指标数据
        """
        try:
            # 获取技术指标数据（模拟实现）
            # 实际应使用akshare的相关接口获取真实数据
            tech_data = {
                "macd_dif": np.random.uniform(-0.5, 1),
                "macd_dea": np.random.uniform(-0.5, 1),
                "kdj_k": np.random.uniform(20, 90),
                "kdj_d": np.random.uniform(20, 90),
                "rsi_6": np.random.uniform(30, 80),
                "boll_position": np.random.uniform(0, 2),  # 布林带位置
                "ma5": np.random.uniform(10, 50),  # 5日均线
                "ma20": np.random.uniform(10, 50),  # 20日均线
                "ma60": np.random.uniform(10, 50),  # 60日均线
                "above_ma5": np.random.choice([True, False]),  # 是否站上5日线
                "above_ma20": np.random.choice([True, False]),  # 是否站上20日线
                "ma_alignment": np.random.choice([True, False])  # 均线多头排列
            }
            self.logger.debug(f"股票 {stock_code} 技术指标数据: {tech_data}")
            return tech_data
        except Exception as e:
            self.logger.error(f"获取股票{stock_code}技术指标失败: {e}")
            return {}
    
    def get_market_environment(self):
        """
        获取市场环境数据
        
        Returns:
        dict: 市场环境信息
        """
        try:
            # 获取上证指数数据
            index_data = ak.stock_zh_index_daily(symbol="sh000001")
            if len(index_data) > 20:
                ma20 = index_data['close'].tail(20).mean()
                current_price = index_data['close'].iloc[-1]
                above_ma20 = current_price > ma20
            else:
                above_ma20 = True
            
            market_env = {
                "above_ma20": above_ma20,  # 上证指数是否在20日均线上方
                "market_sentiment": np.random.uniform(40, 90),  # 市场情绪指标
                "max_limit_up_count": np.random.randint(2, 8),  # 市场最高连板数
                "first_limit_up_success_rate": np.random.uniform(0.1, 0.6)  # 昨日首板晋级率
            }
            self.logger.info(f"市场环境数据: {market_env}")
            return market_env
        except Exception as e:
            self.logger.error(f"获取市场环境数据失败: {e}")
            return {
                "above_ma20": True,
                "market_sentiment": 50,
                "max_limit_up_count": 3,
                "first_limit_up_success_rate": 0.3
            }
    
    def get_plate_effect(self, stock_code):
        """
        获取板块效应数据
        
        Parameters:
        stock_code (str): 股票代码
        
        Returns:
        dict: 板块效应信息
        """
        try:
            plate_data = {
                "follower_count": np.random.randint(0, 5),  # 跟风涨停数
                "plate_strength_rank": np.random.randint(1, 30),  # 板块强度排名
                "main_fund_flow": np.random.choice([True, False])  # 主力资金净流入
            }
            self.logger.debug(f"股票 {stock_code} 板块效应数据: {plate_data}")
            return plate_data
        except Exception as e:
            self.logger.error(f"获取股票{stock_code}板块效应数据失败: {e}")
            return {
                "follower_count": 0,
                "plate_strength_rank": 30,
                "main_fund_flow": False
            }
    
    def calculate_limit_up_quality_score(self, stock_data):
        """
        计算封板质量得分（满分30分）
        
        Parameters:
        stock_data (dict): 股票数据
        
        Returns:
        float: 得分
        """
        score = 0
        
        # 涨停时间得分（10分）
        limit_time = stock_data.get("涨停时间")
        # 只有当涨停时间存在且不为默认值时才计算得分
        if limit_time and limit_time != "15:00":
            if limit_time <= "10:00":
                score += 10
            elif limit_time <= "10:30":
                score += 8
            else:
                score += 5
        else:
            # 当涨停时间不可用时，给予较低的基础分
            score += 3
            
        # 封单金额占比得分（10分）
        limit_amount_ratio = stock_data.get("封单金额占比", 0)
        if limit_amount_ratio >= 0.03:  # 3%以上
            score += 10
        elif limit_amount_ratio >= 0.025:  # 2.5%以上
            score += 8
        else:
            score += 5
            
        # 开板次数得分（5分）
        open_times = stock_data.get("开板次数", 2)
        if open_times == 0:
            score += 5
        elif open_times == 1:
            score += 3
        else:
            score += 1
            
        # 封板后抛压得分（5分）
        sell_pressure = stock_data.get("封板后抛压", 0.3)
        if sell_pressure <= 0.1:
            score += 5
        elif sell_pressure <= 0.2:
            score += 3
        else:
            score += 1
            
        return min(score, 30)  # 不超过满分
    
    def calculate_technical_score(self, tech_data):
        """
        计算技术面得分（满分25分）
        
        Parameters:
        tech_data (dict): 技术指标数据
        
        Returns:
        float: 得分
        """
        score = 0
        
        # 均线系统得分（10分）
        if tech_data.get("above_ma5", False) and tech_data.get("above_ma20", False):
            score += 5
            if tech_data.get("ma_alignment", False):
                score += 5
                
        # MACD得分（5分）
        dif = tech_data.get("macd_dif", 0)
        dea = tech_data.get("macd_dea", 0)
        if dif > dea and dif > 0:
            score += 5
            
        # KDJ得分（5分）
        k = tech_data.get("kdj_k", 50)
        d = tech_data.get("kdj_d", 50)
        if k > d and 50 <= k <= 80:
            score += 5
            
        # RSI得分（5分）
        rsi = tech_data.get("rsi_6", 50)
        if 40 <= rsi <= 70:
            score += 5
            
        return min(score, 25)  # 不超过满分
    
    def calculate_fundamental_score(self, stock_data):
        """
        计算基本面得分（满分20分）
        
        Parameters:
        stock_data (dict): 股票基本数据
        
        Returns:
        float: 得分
        """
        score = 0
        
        # 市值得分（10分）
        market_value = stock_data.get("流通市值", 0) / 100000000  # 转换为亿元
        if 15 <= market_value <= 80:
            score += 10
        elif 10 <= market_value <= 100:
            score += 5
            
        # 换手率得分（5分）
        turnover = stock_data.get("换手率", 0)
        if 3 <= turnover <= 12:
            score += 5
        elif 2 <= turnover <= 15:
            score += 3
            
        # 量比得分（5分）
        volume_ratio = stock_data.get("量比", 0)
        vr_range = self.enhanced_params["volume_ratio_range"]
        if vr_range[0] <= volume_ratio <= vr_range[1]:
            score += 5
        elif volume_ratio > 0:
            score += 2
            
        return min(score, 20)  # 不超过满分
    
    def calculate_market_environment_score(self, market_env, plate_data):
        """
        计算市场环境得分（满分15分）
        
        Parameters:
        market_env (dict): 市场环境数据
        plate_data (dict): 板块效应数据
        
        Returns:
        float: 得分
        """
        score = 0
        
        # 大盘环境得分（5分）
        if market_env.get("above_ma20", False):
            score += 3
        if market_env.get("market_sentiment", 0) >= self.enhanced_params["market_sentiment_min"]:
            score += 2
            
        # 板块效应得分（5分）
        if plate_data.get("follower_count", 0) >= 2:
            score += 2
        if plate_data.get("plate_strength_rank", 30) <= self.enhanced_params["plate_strength_rank"]:
            score += 2
        if plate_data.get("main_fund_flow", False):
            score += 1
            
        # 连板生态得分（5分）
        if market_env.get("max_limit_up_count", 0) >= 3:
            score += 3
        if market_env.get("first_limit_up_success_rate", 0) >= 0.3:
            score += 2
            
        return min(score, 15)  # 不超过满分
    
    def calculate_risk_control_score(self, stock_data, chip_data, tech_data):
        """
        计算风险控制得分（满分10分）
        
        Parameters:
        stock_data (dict): 股票数据
        chip_data (dict): 筹码分布数据
        tech_data (dict): 技术指标数据
        
        Returns:
        float: 得分
        """
        score = 0
        
        # 筹码分析得分（4分）
        if chip_data.get("profit_ratio", 0) >= self.enhanced_params["profit_ratio_min"]:
            score += 2
        if chip_data.get("concentration_90", 100) <= self.enhanced_params["chip_concentration_max"]:
            score += 1
        if not chip_data.get("recent_unblock_pressure", True):
            score += 1
            
        # 技术风险得分（3分）
        # 距离60日均线涨幅控制
        ma60 = tech_data.get("ma60", 1)
        current_price = stock_data.get("最新价", ma60)
        if ma60 > 0 and (current_price / ma60 - 1) <= 0.3:
            score += 2
            
        # 布林带位置（避免高位）
        if tech_data.get("boll_position", 1) <= 1.5:
            score += 1
            
        # 流动性风险得分（3分）
        amount = stock_data.get("成交额", 0) / 100000000  # 亿元
        if amount >= 1:
            score += 2
        elif amount >= 0.5:
            score += 1
            
        return min(score, 10)  # 不超过满分
    
    def check_limit_up_gene_criteria(self, limit_history):
        """
        检查涨停基因条件
        
        Parameters:
        limit_history (int): 历史涨停次数
        
        Returns:
        bool: 是否符合涨停基因条件
        """
        return limit_history >= 3
    
    def check_limit_up_type_criteria(self, limit_type):
        """
        检查板型排除条件
        
        Parameters:
        limit_type (str): 涨停板类型
        
        Returns:
        bool: 是否符合板型条件（即不是需要排除的类型）
        """
        return limit_type not in ["一字板", "T字板"]
    
    def check_limit_up_time_criteria(self, limit_time):
        """
        检查涨停时间条件
        
        Parameters:
        limit_time (str): 涨停时间
        
        Returns:
        bool: 是否符合涨停时间条件
        """
        # 只有当涨停时间存在且不是默认值时才检查
        if limit_time and limit_time != "15:00":
            return limit_time <= "10:30"
        # 当涨停时间不可用时，默认通过检查
        return True
    
    def check_limit_up_amount_criteria(self, limit_amount_ratio):
        """
        检查封单金额条件
        
        Parameters:
        limit_amount_ratio (float): 封单金额占比
        
        Returns:
        bool: 是否符合封单金额条件
        """
        return limit_amount_ratio >= 0.025
    
    def check_open_times_criteria(self, open_times):
        """
        检查开板次数条件
        
        Parameters:
        open_times (int): 开板次数
        
        Returns:
        bool: 是否符合开板次数条件
        """
        return open_times <= 1
    
    def check_turnover_criteria(self, turnover):
        """
        检查换手率条件
        
        Parameters:
        turnover (float): 换手率
        
        Returns:
        bool: 是否符合换手率条件
        """
        return 3 <= turnover <= 12
    
    def check_volume_ratio_criteria(self, volume_ratio):
        """
        检查量比条件
        
        Parameters:
        volume_ratio (float): 量比
        
        Returns:
        bool: 是否符合量比条件
        """
        vr_range = self.enhanced_params["volume_ratio_range"]
        return vr_range[0] <= volume_ratio <= vr_range[1]
    
    def check_amount_criteria(self, amount):
        """
        检查成交额条件
        
        Parameters:
        amount (float): 成交额（亿元）
        
        Returns:
        bool: 是否符合成交额条件
        """
        return 2 <= amount <= 20
    
    def check_market_value_criteria(self, market_value, total_market_value):
        """
        检查市值条件
        
        Parameters:
        market_value (float): 流通市值（亿元）
        total_market_value (float): 总市值（亿元）
        
        Returns:
        bool: 是否符合市值条件
        """
        return 15 <= market_value <= 80 and total_market_value <= 150
    
    def filter_limit_up_stocks(self, stocks_df):
        """
        筛选涨停板股票
        
        Parameters:
        stocks_df (DataFrame): 涨停板股票数据
        
        Returns:
        list: 筛选后的股票列表
        """
        filtered_stocks = []
        screening_details = []
        
        self.logger.info(f"开始筛选 {len(stocks_df)} 只涨停股票")
        
        # 获取市场环境数据
        market_env = self.get_market_environment()
        
        for index, row in stocks_df.iterrows():
            try:
                stock_info = row.to_dict()
                stock_code = row.get("代码", "")
                
                self.logger.info(f"正在处理股票: {stock_code} - {row.get('名称', '')}")
                
                # 获取股票详细数据
                limit_history = self.get_stock_limit_history(stock_code)
                chip_data = self.get_stock_chip_distribution(stock_code)
                tech_data = self.get_stock_technical_indicators(stock_code)
                plate_data = self.get_plate_effect(stock_code)
                
                # 获取各项筛选条件的值
                limit_type = row.get("涨停板类型", "")
                limit_time = row.get("涨停时间", "15:00")  # 设置默认值为15:00（收盘前涨停）
                limit_amount_ratio = row.get("封单金额", 0) / (row.get("流通市值", 1) or 1)
                open_times = row.get("开板次数", 2)
                turnover = row.get("换手率", 0)
                volume_ratio = row.get("量比", 0)
                amount = row.get("成交额", 0) / 100000000  # 亿元
                market_value = row.get("流通市值", 0) / 100000000  # 亿元
                total_market_value = row.get("总市值", 0) / 100000000  # 亿元
                
                # 检查各项筛选条件
                gene_criteria = self.check_limit_up_gene_criteria(limit_history)
                type_criteria = self.check_limit_up_type_criteria(limit_type)
                # 确保limit_time是字符串类型，防止None值导致错误
                limit_time_str = str(limit_time) if pd.notna(limit_time) else "15:00"
                time_criteria = self.check_limit_up_time_criteria(limit_time_str)
                amount_criteria = self.check_limit_up_amount_criteria(limit_amount_ratio)
                open_times_criteria = self.check_open_times_criteria(open_times)
                turnover_criteria = self.check_turnover_criteria(turnover)
                volume_ratio_criteria = self.check_volume_ratio_criteria(volume_ratio)
                amount_criteria2 = self.check_amount_criteria(amount)
                market_value_criteria = self.check_market_value_criteria(market_value, total_market_value)
                
                # 记录筛选详情
                screening_details.append({
                    "代码": stock_code,
                    "名称": row.get("名称", ""),
                    "涨停时间": limit_time if pd.notna(limit_time) and limit_time != "15:00" else "尾盘涨停",
                    "最新价": row.get("最新价", 0),
                    "成交额(亿元)": amount,
                    "换手率(%)": turnover,
                    "量比": volume_ratio,
                    "流通市值(亿元)": market_value,
                    "总市值(亿元)": total_market_value,
                    "涨停基因(≥3次)": "是" if gene_criteria else "否",
                    "板型排除(非一字/T字)": "是" if type_criteria else "否",
                    "涨停时间(≤10:30)": "是" if time_criteria else "否",
                    "封单金额(≥2.5%)": "是" if amount_criteria else "否",
                    "开板次数(≤1次)": "是" if open_times_criteria else "否",
                    "换手率(3-12%)": "是" if turnover_criteria else "否",
                    "量比(1.5-3.0)": "是" if volume_ratio_criteria else "否",
                    "成交额(2-20亿)": "是" if amount_criteria2 else "否",
                    "市值(15-80亿流通)": "是" if market_value_criteria else "否"
                })
                
                # 板型排除：排除一字板、T字板等
                if not type_criteria:
                    self.logger.info(f"股票 {stock_code} 被排除：板型为 {limit_type}")
                    continue
                    
                # 首板质量强化筛选
                # 1. 涨停基因筛选
                if not gene_criteria:
                    self.logger.info(f"股票 {stock_code} 被排除：涨停次数不足 ({limit_history} < 3)")
                    continue
                    
                # 2. 封板质量筛选
                if not time_criteria:
                    self.logger.info(f"股票 {stock_code} 被排除：涨停时间过晚 ({limit_time})")
                    continue
                    
                if not amount_criteria:
                    self.logger.info(f"股票 {stock_code} 被排除：封单金额占比不足 ({limit_amount_ratio:.4f} < 0.025)")
                    continue
                    
                if not open_times_criteria:
                    self.logger.info(f"股票 {stock_code} 被排除：开板次数过多 ({open_times} > 1)")
                    continue
                    
                # 3. 量价关系筛选
                if not turnover_criteria:
                    self.logger.info(f"股票 {stock_code} 被排除：换手率不符合要求 ({turnover} 不在 3-12 范围内)")
                    continue
                    
                if not volume_ratio_criteria:
                    self.logger.info(f"股票 {stock_code} 被排除：量比不符合要求 ({volume_ratio} 不在 {self.enhanced_params['volume_ratio_range']} 范围内)")
                    continue
                    
                if not amount_criteria2:
                    self.logger.info(f"股票 {stock_code} 被排除：成交额不符合要求 ({amount} 亿元不在 2-20 亿元范围内)")
                    continue
                    
                # 4. 市值筛选
                if not market_value_criteria:
                    self.logger.info(f"股票 {stock_code} 被排除：市值不符合要求 (流通{market_value}亿元或总{total_market_value}亿元不符合条件)")
                    continue
                    
                # 通过筛选的股票进行评分
                limit_up_score = self.calculate_limit_up_quality_score(stock_info)
                technical_score = self.calculate_technical_score(tech_data)
                fundamental_score = self.calculate_fundamental_score(stock_info)
                market_score = self.calculate_market_environment_score(market_env, plate_data)
                risk_score = self.calculate_risk_control_score(stock_info, chip_data, tech_data)
                
                total_score = limit_up_score + technical_score + fundamental_score + market_score + risk_score
                
                # 添加评分信息
                stock_info["封板质量得分"] = limit_up_score
                stock_info["技术面得分"] = technical_score
                stock_info["基本面得分"] = fundamental_score
                stock_info["市场环境得分"] = market_score
                stock_info["风险控制得分"] = risk_score
                stock_info["总得分"] = total_score
                
                self.logger.info(f"股票 {stock_code} 通过筛选，总得分: {total_score:.2f}")
                filtered_stocks.append(stock_info)
                
            except Exception as e:
                self.logger.error(f"处理股票{stock_code}时出错: {e}")
                continue
                
        self.logger.info(f"筛选完成，共 {len(filtered_stocks)} 只股票通过筛选")
        
        # 保存筛选详情到Excel文件，按最新价从低到高排序
        if screening_details:
            screening_df = pd.DataFrame(screening_details)
            screening_df = screening_df.sort_values(by="最新价", ascending=True)  # 按最新价从低到高排序
            screening_df.to_excel('all_limit_up_stocks_screening_details.xlsx', index=False)
            self.logger.info("所有涨停股票筛选详情已保存到 all_limit_up_stocks_screening_details.xlsx")
        
        return filtered_stocks
    
    def classify_stocks(self, stocks_list):
        """
        对股票进行分级
        
        Parameters:
        stocks_list (list): 股票列表
        
        Returns:
        dict: 分级结果
        """
        top_recommendations = []  # 重点推荐（得分≥85）
        general_attention = []    # 一般关注（得分70-84）
        risk_warning = []         # 风险提示（得分<70）
        
        for stock in stocks_list:
            score = stock.get("总得分", 0)
            if score >= 85:
                top_recommendations.append(stock)
            elif score >= 70:
                general_attention.append(stock)
            else:
                risk_warning.append(stock)
                
        self.logger.info(f"分级结果：重点推荐 {len(top_recommendations)} 只，一般关注 {len(general_attention)} 只，风险提示 {len(risk_warning)} 只")
        return {
            "重点推荐": top_recommendations,
            "一般关注": general_attention,
            "风险提示": risk_warning
        }
    
    def create_summary_dataframe(self, results):
        """
        创建汇总数据表格
        
        Parameters:
        results (dict): 筛选结果
        
        Returns:
        DataFrame: 汇总数据表格
        """
        summary_data = []
        
        for category, stocks in results.items():
            for stock in stocks:
                limit_time = stock.get("涨停时间", "")
                # 修复显示问题，当涨停时间为默认值15:00时显示为无数据
                if not limit_time or limit_time == "15:00":
                    limit_time = "无数据"
                    
                summary_data.append({
                    "分类": category,
                    "代码": stock.get("代码", ""),
                    "名称": stock.get("名称", ""),
                    "总得分": stock.get("总得分", 0),
                    "封板质量得分": stock.get("封板质量得分", 0),
                    "技术面得分": stock.get("技术面得分", 0),
                    "基本面得分": stock.get("基本面得分", 0),
                    "市场环境得分": stock.get("市场环境得分", 0),
                    "风险控制得分": stock.get("风险控制得分", 0),
                    "涨跌幅(%)": stock.get("涨跌幅", 0),
                    "换手率(%)": stock.get("换手率", 0),
                    "成交额(亿元)": stock.get("成交额", 0) / 100000000,
                    "流通市值(亿元)": stock.get("流通市值", 0) / 100000000,
                    "涨停时间": limit_time
                })
        
        df = pd.DataFrame(summary_data)
        if not df.empty:
            df = df.sort_values(by=["分类", "总得分"], ascending=[True, False])
        return df
    
    def create_visualizations(self, df):
        """
        创建可视化图表
        
        Parameters:
        df (DataFrame): 汇总数据表格
        """
        if df.empty:
            self.logger.warning("数据为空，无法生成图表")
            return
        
        # 创建图表
        fig, axes = plt.subplots(2, 2, figsize=(15, 12))
        fig.suptitle('股票一进二板量化筛选分析结果', fontsize=16)
        
        # 1. 各分类股票数量
        category_counts = df['分类'].value_counts()
        axes[0, 0].bar(category_counts.index, category_counts.values, color=['red', 'orange', 'gray'])
        axes[0, 0].set_title('各分类股票数量')
        axes[0, 0].set_ylabel('数量')
        for i, v in enumerate(category_counts.values):
            axes[0, 0].text(i, v, str(v), ha='center', va='bottom')
        
        # 2. 总得分分布
        axes[0, 1].hist(df['总得分'], bins=20, color='skyblue', edgecolor='black')
        axes[0, 1].set_title('总得分分布')
        axes[0, 1].set_xlabel('总得分')
        axes[0, 1].set_ylabel('频数')
        
        # 3. 各维度得分雷达图（仅展示前几只重点推荐股）
        top_stocks = df[df['分类'] == '重点推荐'].head(5)
        if not top_stocks.empty:
            categories = ['封板质量得分', '技术面得分', '基本面得分', '市场环境得分', '风险控制得分']
            ax = plt.subplot(2, 2, 3, projection='polar')
            
            angles = np.linspace(0, 2 * np.pi, len(categories), endpoint=False)
            angles = np.concatenate((angles, [angles[0]]))  # 闭合图形
            
            for _, stock in top_stocks.iterrows():
                values = [stock[cat]/30*10 for cat in categories]  # 归一化到10分制
                values = np.concatenate((values, [values[0]]))  # 闭合图形
                ax.plot(angles, values, 'o-', linewidth=2, label=stock['名称'])
                ax.fill(angles, values, alpha=0.25)
            
            ax.set_xticks(angles[:-1])
            ax.set_xticklabels(categories)
            ax.set_ylim(0, 10)
            ax.set_title('重点推荐股各维度得分雷达图')
            ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.0))
        else:
            axes[1, 0].text(0.5, 0.5, '无重点推荐股票', ha='center', va='center')
            axes[1, 0].set_title('重点推荐股各维度得分')
        
        # 4. 成交额与得分关系
        scatter = axes[1, 1].scatter(df['成交额(亿元)'], df['总得分'], c=df['总得分'], cmap='viridis')
        axes[1, 1].set_xlabel('成交额(亿元)')
        axes[1, 1].set_ylabel('总得分')
        axes[1, 1].set_title('成交额与总得分关系')
        plt.colorbar(scatter, ax=axes[1, 1])
        
        plt.tight_layout()
        plt.savefig('stock_analysis_results.png', dpi=300, bbox_inches='tight')
        self.logger.info("图表已保存为 stock_analysis_results.png")
        plt.show()
    
    def create_echarts_visualization(self, df):
        """
        创建Echarts可视化图表
        
        Parameters:
        df (DataFrame): 汇总数据表格
        """
        if df.empty:
            self.logger.warning("数据为空，无法生成Echarts图表")
            return
        
        # 生成Echarts HTML文件
        html_content = """
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>股票一进二板量化筛选分析结果</title>
    <script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
    <style>
        .chart-container {
            width: 100%;
            height: 500px;
            margin-bottom: 20px;
        }
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
    </style>
</head>
<body>
    <h1>股票一进二板量化筛选分析结果</h1>
    
    <h2>各分类股票数量</h2>
    <div id="categoryChart" class="chart-container"></div>
    
    <h2>总得分分布</h2>
    <div id="scoreDistributionChart" class="chart-container"></div>
    
    <h2>成交额与总得分关系</h2>
    <div id="amountScoreChart" class="chart-container"></div>
    
    <h2>重点推荐股票</h2>
    <div id="topStocksChart" class="chart-container"></div>
    
    <script>
        // 各分类股票数量
        var categoryChart = echarts.init(document.getElementById('categoryChart'));
        var categoryOption = {
            title: {
                text: '各分类股票数量'
            },
            tooltip: {},
            xAxis: {
                type: 'category',
                data: """ + str(df['分类'].value_counts().index.tolist()) + """
            },
            yAxis: {
                type: 'value'
            },
            series: [{
                data: """ + str(df['分类'].value_counts().values.tolist()) + """",
                type: 'bar',
                itemStyle: {
                    color: function(params) {
                        var colorList = ['red', 'orange', 'gray'];
                        return colorList[params.dataIndex];
                    }
                }
            }]
        };
        categoryChart.setOption(categoryOption);
        
        // 总得分分布
        var scoreDistributionChart = echarts.init(document.getElementById('scoreDistributionChart'));
        var scores = """ + str(df['总得分'].tolist()) + """;
        var scoreDistributionOption = {
            title: {
                text: '总得分分布'
            },
            tooltip: {},
            xAxis: {
                type: 'value',
                name: '总得分'
            },
            yAxis: {
                type: 'value',
                name: '频数'
            },
            series: [{
                data: scores.map(function(score) { return [score, 1]; }),
                type: 'scatter',
                symbolSize: 10
            }]
        };
        scoreDistributionChart.setOption(scoreDistributionOption);
        
        // 成交额与总得分关系
        var amountScoreChart = echarts.init(document.getElementById('amountScoreChart'));
        var amountScoreData = """ + str([[row['成交额(亿元)'], row['总得分']] for _, row in df.iterrows()]) + """;
        var amountScoreOption = {
            title: {
                text: '成交额与总得分关系'
            },
            tooltip: {
                trigger: 'item',
                formatter: function(params) {
                    return '成交额: ' + params.value[0] + '亿元<br/>' +
                           '总得分: ' + params.value[1];
                }
            },
            xAxis: {
                type: 'value',
                name: '成交额(亿元)'
            },
            yAxis: {
                type: 'value',
                name: '总得分'
            },
            visualMap: {
                min: Math.min(...amountScoreData.map(item => item[1])),
                max: Math.max(...amountScoreData.map(item => item[1])),
                inRange: {
                    color: ['blue', 'green', 'yellow', 'red']
                },
                calculable: true
            },
            series: [{
                data: amountScoreData,
                type: 'scatter',
                symbolSize: 15
            }]
        };
        amountScoreChart.setOption(amountScoreOption);
        
        // 重点推荐股票
        var topStocksChart = echarts.init(document.getElementById('topStocksChart'));
        var topStocks = """ + str(df[df['分类'] == '重点推荐'].head(10)[['名称', '总得分', '封板质量得分', '技术面得分', '基本面得分', '市场环境得分', '风险控制得分']].to_dict('records')).replace('NaN', '0') + """;
        var topStocksSeries = [];
        var topStocksCategories = ['总得分', '封板质量得分', '技术面得分', '基本面得分', '市场环境得分', '风险控制得分'];
        
        if (topStocks.length > 0) {
            topStocksSeries = topStocks.map(function(stock) {
                return {
                    name: stock['名称'],
                    type: 'bar',
                    stack: 'total',
                    label: {
                        show: true
                    },
                    emphasis: {
                        focus: 'series'
                    },
                    data: [
                        stock['总得分'] || 0,
                        stock['封板质量得分'] || 0,
                        stock['技术面得分'] || 0,
                        stock['基本面得分'] || 0,
                        stock['市场环境得分'] || 0,
                        stock['风险控制得分'] || 0
                    ]
                };
            });
        }
        
        var topStocksOption = {
            title: {
                text: '重点推荐股票各维度得分'
            },
            tooltip: {
                trigger: 'axis',
                axisPointer: {
                    type: 'shadow'
                }
            },
            legend: {
                data: topStocks.map(function(stock) { return stock['名称']; })
            },
            xAxis: {
                type: 'category',
                data: topStocksCategories
            },
            yAxis: {
                type: 'value'
            },
            series: topStocksSeries
        };
        topStocksChart.setOption(topStocksOption);
    </script>
</body>
</html>
        """
        
        with open('stock_analysis_echarts.html', 'w', encoding='utf-8') as f:
            f.write(html_content)
        
        self.logger.info("Echarts图表已保存为 stock_analysis_echarts.html")
    
    def select_stocks(self, date=None):
        """
        主筛选函数
        
        Parameters:
        date (str): 日期，格式为YYYYMMDD
        
        Returns:
        dict: 筛选结果
        """
        self.logger.info("开始获取涨停板数据...")
        limit_up_stocks = self.get_latest_limit_up_stocks(date)
        
        if limit_up_stocks.empty:
            self.logger.warning("未获取到涨停板数据")
            return {}
            
        self.logger.info(f"获取到{len(limit_up_stocks)}只涨停股票，开始筛选...")
        
        # 筛选股票
        filtered_stocks = self.filter_limit_up_stocks(limit_up_stocks)
        
        # 对股票进行分级
        classified_stocks = self.classify_stocks(filtered_stocks)
        
        return classified_stocks
    
    def print_results(self, results):
        """
        打印筛选结果
        
        Parameters:
        results (dict): 筛选结果
        """
        print("\n" + "="*60)
        print("股票一进二板量化筛选系统 - 优化版")
        print("="*60)
        
        for category, stocks in results.items():
            print(f"\n【{category}】(共{len(stocks)}只):")
            print("-" * 50)
            
            # 按得分排序
            sorted_stocks = sorted(stocks, key=lambda x: x.get("总得分", 0), reverse=True)
            
            for i, stock in enumerate(sorted_stocks, 1):
                code = stock.get("代码", "")
                name = stock.get("名称", "")
                score = stock.get("总得分", 0)
                change = stock.get("涨跌幅", 0)
                turnover = stock.get("换手率", 0)
                limit_time = stock.get("涨停时间", "")
                # 修复显示问题，当涨停时间为默认值15:00时显示为无数据
                if not limit_time or limit_time == "15:00":
                    limit_time = "无数据"
                
                print(f"{i:2d}. {code} {name} | 得分:{score:5.1f} | "
                      f"涨幅:{change:5.2f}% | 换手:{turnover:5.2f}% | "
                      f"涨停时间:{limit_time}")
                
                # 显示详细评分（仅对重点推荐显示）
                if category == "重点推荐":
                    limit_score = stock.get("封板质量得分", 0)
                    tech_score = stock.get("技术面得分", 0)
                    fund_score = stock.get("基本面得分", 0)
                    market_score = stock.get("市场环境得分", 0)
                    risk_score = stock.get("风险控制得分", 0)
                    print(f"    封板:{limit_score:4.1f} 技术:{tech_score:4.1f} "
                          f"基本面:{fund_score:4.1f} 市场:{market_score:4.1f} 风控:{risk_score:4.1f}")
        
        # 输出统计信息
        total_count = sum(len(stocks) for stocks in results.values())
        print(f"\n总计筛选出 {total_count} 只符合条件的股票")
        
        # 创建汇总数据表格
        summary_df = self.create_summary_dataframe(results)
        print("\n" + "="*60)
        print("汇总数据表格")
        print("="*60)
        if not summary_df.empty:
            print(summary_df.to_string(index=False))
            # 保存到Excel文件
            summary_df.to_excel('stock_selection_results.xlsx', index=False)
            self.logger.info("结果已保存到 stock_selection_results.xlsx")
            
            # 创建可视化图表
            self.create_visualizations(summary_df)
            
            # 创建Echarts可视化图表
            self.create_echarts_visualization(summary_df)
        else:
            print("无符合条件的股票")


def main():
    """
    主函数
    """
    # 更新配置文件，添加增强参数
    try:
        with open("config.json", "r", encoding="utf-8") as f:
            config = json.load(f)
        
        # 添加增强参数
        if "enhanced_params" not in config:
            config["enhanced_params"] = {
                "score_threshold": 75,
                "volume_ratio_range": [1.5, 3.0],
                "profit_ratio_min": 60,
                "chip_concentration_max": 15,
                "market_sentiment_min": 50,
                "plate_strength_rank": 10
            }
            
        with open("config.json", "w", encoding="utf-8") as f:
            json.dump(config, f, ensure_ascii=False, indent=4)
    except Exception as e:
        print(f"更新配置文件失败: {e}")
    
    # 创建筛选器实例
    selector = EnhancedStockSelector()
    
    # 执行筛选
    results = selector.select_stocks()
    
    # 打印结果
    if results:
        selector.print_results(results)
    else:
        print("未筛选到符合条件的股票")


if __name__ == "__main__":
    main()